import math
import re
import sys
from typing import List

import datefinder
from bs4 import BeautifulSoup
DATE_FORMAT = "%Y/%m/%d"
FISCAL_KEY = 'fiscal year end'
FISCAL_SECONDARY_KEY = 'year end'

FAILED_TO_GET_RATIO = 'Failed to get ratio'
FISCAL_ERROR = 'FISCAL_ERROR'
N_SENTENCE = 2
RADIUS = 1000


def _get_digit(exp: str):
    exp = exp.replace(',', '')
    exp = exp.replace('$', '')
    return exp


def _get_digit_ratio(ratio_param: str):
    ratio = ratio_param
    ratio = ratio.replace(',', '')
    ratio = ratio.replace('times the median', '')
    ratio = ratio.replace('to one', '')
    pd = r'([\d.]+)'
    m = re.findall(pd, ratio)
    if m:
        for value in m:
            if float(value) > 1:
                return value
    return 'FAILED_TO_GET_RATIO'


def _merge_intervals(intervals: List[List[int]]) -> List[List[int]]:
    intervals.sort(key=lambda x: x[0])

    merged = []
    for interval in intervals:
        # 如果列表为空，或者当前区间与上一区间不重合，直接添加
        if not merged or merged[-1][1] < interval[0]:
            merged.append(interval)
        else:
            # 否则的话，我们就可以与上一区间进行合并
            merged[-1][1] = max(merged[-1][1], interval[1])

    return merged


def _find_pos_before_n_sentence(content: str, position: int, n: int) -> int:
    dot_pos = position
    while n > 0:
        dot_pos = content.rfind('.', 0, dot_pos)
        n -= 1
    return dot_pos


def _find_pos_after_n_sentence(content: str, position: int, n: int) -> int:
    dot_pos = position
    while n > 0:
        dot_pos = content.find('.', dot_pos)
        n -= 1
    return dot_pos


def _find_ceo_emp_index(region: str, pos: int):
    ceo_distance = region.rfind('ceo', 0, pos)
    emp_distance = region.rfind('employee', 0, pos)
    if emp_distance < 0:
        emp_distance = region.rfind('median', 0, pos)

    # ceo_distance = ceo_distance if ceo_distance > 0 else sys.maxsize
    # emp_distance = emp_distance if emp_distance > 0 else sys.maxsize
    return [ceo_distance, emp_distance]


class RatioParser:
    pay_ratio_pattern = 'pay ratio'
    compensation_pattern = 'compensation'
    pay_amount_pattern = "\$[\d,\.]+\d"

    def __init__(self, html: str):
        soup = BeautifulSoup(html, 'lxml')
        self.txt = soup.get_text()
        self.lower_txt = self.txt.lower()
        self.fiscal_year_end_sentence = None

    def _get_ratio_ranges(self):

        pay_ratio_intervals = [[m.start(0) - RADIUS, m.start(0) + RADIUS] for m in
                               re.finditer(RatioParser.pay_ratio_pattern, self.lower_txt)]
        pay_ratio_intervals = _merge_intervals(pay_ratio_intervals)
        valid_regions = []
        for start, end in pay_ratio_intervals:
            region = self.lower_txt[start:end]
            compensation_count = len(re.findall(RatioParser.compensation_pattern, region))
            pay_index = [(m.start(0), m.end(0)) for m in re.finditer(RatioParser.pay_amount_pattern, region)]
            pay_amount_count = len(pay_index)
            if compensation_count == 0 or pay_amount_count == 0:
                continue
            valid_regions.append([start, end])
        return valid_regions

    def get_compensation(self):
        # $11,708,230
        valid_regions = self._get_ratio_ranges()
        if len(valid_regions) != 1:
            return "NOT_ONLY_ONE_REGION", "NOT_ONLY_ONE_REGION"

        region = self.lower_txt[valid_regions[0][0]: valid_regions[0][1]]
        pay_index = [(m.start(0), m.end(0)) for m in re.finditer(RatioParser.pay_amount_pattern, region)]

        ceo_compensation = None
        emp_compensation = None
        ceo_distances = []
        emp_distances = []
        amounts = []  # List[str]
        for amount_start, amount_end in pay_index:
            ceo_dist, emp_dist = _find_ceo_emp_index(region, amount_start)
            amount = _get_digit(region[amount_start:amount_end])
            ceo_distances.append(ceo_dist)
            emp_distances.append(emp_dist)
            amounts.append(amount)

        max_ceo_index = None
        for i, d in enumerate(ceo_distances):
            if max_ceo_index is None or max_ceo_index < d:
                max_ceo_index = d
                ceo_compensation = amounts[i]

        map_emp_index = None
        for i, d in enumerate(emp_distances):
            if map_emp_index is None or map_emp_index < d:
                map_emp_index = d
                emp_compensation = amounts[i]

        # if ceo_compensation is None or emp_compensation is None:  # 没有正好匹配，就按大小排
        #     p1 = region[pay_index[0][0]: pay_index[0][1]]
        #     p2 = region[pay_index[1][0]: pay_index[1][1]]
        #     p1d = int(_get_digit(p1))
        #     p2d = int(_get_digit(p2))
        #     ceo_compensation = max(p1d, p2d)
        #     emp_compensation = min(p1d, p2d)
        # else:
        #     ceo_compensation = int(_get_digit(ceo_compensation))
        #     emp_compensation = int(_get_digit(emp_compensation))
        return ceo_compensation, emp_compensation

    def _rfind_key(self, pos: int):

        pos = self.lower_txt.rfind(FISCAL_KEY, 0, pos)
        if pos < 0:
            pos = self.lower_txt.rfind(FISCAL_SECONDARY_KEY, 0, pos)
        if pos < 0:
            return -1
        return pos

    def get_fiscal_year_end(self):
        pos = len(self.lower_txt)
        while pos > 0:
            pos = self._rfind_key(pos)
            if pos < 0:
                return FISCAL_ERROR

            prev_dot = self.lower_txt.rfind('.', 0, pos)
            next_dot = self.lower_txt.find('.', pos)
            self.fiscal_year_end_sentence = self.txt[prev_dot:next_dot + 1].strip()
            years = re.findall(r'\d{4}', self.fiscal_year_end_sentence)  # regex to get year in sentence
            if years is None:
                continue

            years = [int(x) for x in years]
            cdd = []
            for candidate_date in datefinder.find_dates(self.fiscal_year_end_sentence):
                try:
                    if candidate_date.year in years:
                        cdd.append(candidate_date.strftime(DATE_FORMAT))
                except:
                    pass
            return cdd[-1] if len(cdd) > 0 else FISCAL_ERROR
        return FISCAL_ERROR

    def get_ratio(self):

        ratio = FAILED_TO_GET_RATIO
        patterns = [
            r'[\d.,]+\s*to\s*one',  # 256 to one
            r' one\s*to\s*[\d.,]+',  # one to 256
            r'[\d.,]+\s*times',  # 256 times
            r'[\d,\.]+[-\s]*[to:]+[-\s]*[\d,\.]+'  # 1:256 256:1
        ]
        candidates = []
        for p in patterns:
            candidates.extend(re.findall(p, self.lower_txt))

        candidate_count = len(candidates)
        if candidate_count == 0:
            return ratio
        else:
            distances = [self._find_distance_to_ratio(x) for x in candidates]
            min_idx = -1
            min_dis = len(self.lower_txt)
            for i, d in enumerate(distances):
                if d <= min_dis:
                    min_idx = i
                    min_dis = d

            if min_idx >= 0:
                ratio = candidates[min_idx]

        ratio = _get_digit_ratio(ratio) if ratio != FAILED_TO_GET_RATIO else FAILED_TO_GET_RATIO
        return ratio

    def _find_distance_to_ratio(self, candidate: str):
        candidate = candidate.lower()
        target_keyword = ' ratio '
        candidate_position = self.lower_txt.find(candidate)
        if candidate_position == -1:
            return sys.maxsize
        prev_index = self.lower_txt.rfind(target_keyword, max(candidate_position - RADIUS, 0), candidate_position)
        next_index = self.lower_txt.find(target_keyword, candidate_position,
                                         min(candidate_position + RADIUS, len(self.lower_txt)))
        if prev_index == -1 and next_index == -1:
            return sys.maxsize

        nd = math.fabs(next_index - candidate_position)
        if prev_index == -1:
            return nd

        pd = math.fabs(prev_index - candidate_position)
        if next_index == -1:
            return pd

        min_distance = min(pd, nd)
        return min_distance


if __name__ == '__main__':
    with open("D:\git\py-sec.gov\payratio\sample.txt", 'r', encoding='utf-8') as f:
        lines = f.readlines()
    html = ''.join(lines)
    parser = RatioParser(html)
    a = parser.get_ratio()
    print(a)
